﻿module EsercitazioneIntermedia1

//funzioni ausiliarie
let list_of_string (s : string) = s.ToCharArray() |> List.ofArray
let string_of_list (l : char list) = List.foldBack (sprintf "%c%s") l ""

let decode_function =
  [('0',[]);
   ('1',[]);
   ('2',['a';'b';'c']);
   ('3',['d';'e';'f']);
   ('4',['g';'h';'i']);
   ('5',['j';'k';'l']);
   ('6',['m';'n';'o']);
   ('7',['p';'q';'r';'s']);
   ('8',['t';'u';'v']);
   ('9',['w';'x';'y';'z'])]

let first_one_prefix (s : char list) : char list =
  let rec tail_rec (s : char list) (acc : char list) : char list =
    match s with
    | [] -> acc
    | c :: cs when c = '1' -> acc
    | c :: cs -> tail_rec cs (acc @ [c])
  
  let res = tail_rec s []
  res
   

let rec join (sl1 : string list) (sl2 : string list) =
  if sl2 = [] then
    sl1
  else
    [for s1 in sl1 do
      for s2 in sl2 do
        yield s2 + " " + s1]

let rec combine (sls : string list list)  (acc: string list): string list =
  match sls with
  | [] -> acc
  | sl :: sls1 ->
      let update = join sl acc
      combine sls1 update

let rec prefix (s : char list) (n : int) : char list =
  match s with
  |[] -> []
  |c :: chars when n = 0 -> [c]
  |c :: chars -> c :: (prefix chars (n - 1))

let filter_dictionary (dictionary : string list) (letter : char) (pos : int) : (string list) =
  let tmp = fun (w:string) -> w.Length > pos && w.Chars pos = letter 
  dictionary |> List.filter(tmp)
        

let rec decode_word (code : string) 
                    (dictionary: string list) 
                    (step : int) : string list =

  if code = "" then
    []
  else
    let current_num = code.Chars step
    let possible_letters = decode_function |> List.find(fun (c,e) -> c = current_num) |> snd
    let filtered_dictionary = 
      [for l in possible_letters do
        yield! filter_dictionary dictionary l step]
    if filtered_dictionary = [] then
        let res = dictionary |> List.map(fun w ->
                                          let w_chars = list_of_string w
                                          let w_prefix = prefix w_chars (step - 1)
                                          string_of_list w_prefix)
        res
    elif step = code.Length - 1 then
        let res = filtered_dictionary |> List.map(fun w ->
                                                    let w_chars = list_of_string w
                                                    let w_prefix = prefix w_chars step
                                                    string_of_list w_prefix)
      
        res
    else 
      decode_word code filtered_dictionary (step + 1)

let decode_message (dictionary : string list) (numbers : string) : string list =
  let one_prefix = (first_one_prefix (list_of_string numbers)) |> string_of_list
  let codes = one_prefix.Split([|'0'|]) |> List.ofArray
  let rec decode_words dictionary codes decoded_words =
      match codes with
      |[] -> decoded_words
      |c :: cs -> 
          let dw = decode_word c dictionary 0
          let complete_words = dw |> List.filter(fun w -> dictionary |> List.exists(fun w1 -> w = w1))
          if complete_words <> [] then
            decode_words dictionary cs (decoded_words @ [complete_words])
          else
            decoded_words @ [dw]
  let decodes = decode_words dictionary codes []
  let decoded_words = combine decodes []
  decoded_words
    

//USATE dict1000.txt per 1000 parole dict60000.txt per 60000 parole.
let dictionary_name = 
  "dict60000.txt" // "dict60000.txt"

// MAIN NON TOCCARE
let is_digit d =
  let digits = list_of_string "0123456789"
  digits |> List.exists ((=) d)

// MAIN NON TOCCARE
[<EntryPoint>]
let main args = 
  let uncurry f = fun (x, y) -> f x y
  let rec body dictionary = 
    do printfn "Enter the string message. (Empty to exit)"
    let msg = System.Console.ReadLine()
    if msg <> "" && List.forall is_digit (list_of_string msg) then
      let choices = decode_message dictionary msg
      let choices = choices |> Set.ofList |> List.ofSeq |> List.sort |> List.map (fun x -> (msg, x))
      do List.iter (uncurry (printfn "%s: %s")) choices
      body dictionary
    elif msg <> "" then
      printfn "Error: \"%s\" is not a number" msg
      body dictionary
    else
      printfn "Quit..."
  let dictionary = 
    #if LOCAL
    System.IO.File.ReadAllLines dictionary_name |> List.ofArray
    #else
    System.Console.ReadLine().Split([|' '|]) |> List.ofArray //w_l.Split [|' '|] |> List.ofArray //
    #endif
  let nurico_pls = dictionary |> List.tryFind(fun w -> w = "")
  let dictionary = dictionary |> List.filter(fun w -> w <> "")
  do body dictionary  
  0



//let s = list_of_string "cibo"
//let test = prefix s 3
//System.Console.WriteLine(test)

//let test = decode_message test_dictionary "24260302499"
//System.Console.WriteLine(test)

//let test = combine [["ciao";"cibo"];["d";"e"];["hi";"ho";"hu"]] []
//System.Console.WriteLine(test.ToString())